<?php
/**
 * 用途：将IP转换成地址
 * [code php]
   $ipoper = new IpOperation();
   $addr = $ipoper->convertIP('203.86.0.241');
   //获取明细信息
   $ipoper->getIPArea();
   $country	=	$ipoper->country;
   $province	=	$ipoper->province;
   $city		=	$ipoper->city;
 * [/code]
 *
 * @author hanxing<hanixng@runup.com.hk>
 * @lastmodify 2008-12-26 16:28
 */	
class Ipoperation{
	
	public $area;
	public $country;
	public $province_id;
	public $province;
	public $city;
	public $data_charset;
	public $to_charset;
	public $net_provide_arr;
	public $net_provide;
	public $section_arr;
	public $section;
	
	/**
	 *
	 */
	public function __construct(){
		$this->area[1] = array("北京", "|东城|西城|崇文|宣武|朝阳|丰台|石景山|海淀|门头沟|房山|通州|顺义|昌平|大兴|平谷|怀柔|密云|延庆"); 
		$this->area[2] = array("上海", "|黄浦|卢湾|徐汇|长宁|静安|普陀|闸北|虹口|杨浦|闵行|宝山|嘉定|浦东|金山|松江|青浦|南汇|奉贤|崇明"); 
		$this->area[3] = array("天津", "|和平|东丽|河东|西青|河西|津南|南开|北辰|河北|武清|红挢|塘沽|汉沽|大港|宁河|静海|宝坻|蓟县"); 
		$this->area[4] = array("重庆", "|万州|涪陵|渝中|大渡口|江北|沙坪坝|九龙坡|南岸|北碚|万盛|双挢|渝北|巴南|黔江|长寿|綦江|潼南|铜梁|大足|荣昌|壁山|梁平|城口|丰都|垫江|武隆|忠县|开县|云阳|奉节|巫山|巫溪|石柱|秀山|酉阳|彭水|江津|合川|永川|南川"); 
		$this->area[5] = array("黑龙江", "|哈尔滨|齐齐哈尔|牡丹江|佳木斯|大庆|绥化|鹤岗|鸡西|黑河|双鸭山|伊春|七台河|大兴安岭"); 
		$this->area[6] = array("吉林", "|长春|吉林|四平|辽源|通化|白山|松原|白城|延边"); 
		$this->area[7] = array("辽宁", "|沈阳|大连|鞍山|抚顺|本溪|丹东|锦州|营口|阜新|辽阳|盘锦|铁岭|朝阳|葫芦岛"); 
		$this->area[8] = array("河北", "|石家庄|邯郸|邢台|保定|张家口|承德|廊坊|唐山|秦皇岛|沧州|衡水"); 
		$this->area[9] = array("河南", "|郑州|开封|洛阳|平顶山|安阳|鹤壁|新乡|焦作|濮阳|许昌|漯河|三门峡|南阳|商丘|信阳|周口|驻马店|济源"); 
		$this->area[10] = array("山东", "|济南|青岛|淄博|枣庄|东营|烟台|潍坊|济宁|泰安|威海|日照|莱芜|临沂|德州|聊城|滨州|菏泽"); 
		$this->area[11] = array("山西", "|太原|大同|阳泉|长治|晋城|朔州|吕梁|忻州|晋中|临汾|运城"); 
		$this->area[12] = array("内蒙古", "|呼和浩特|包头|乌海|赤峰|呼伦贝尔盟|阿拉善盟|哲里木盟|兴安盟|乌兰察布盟|锡林郭勒盟|巴彦淖尔盟|伊克昭盟"); 
		$this->area[13] = array("宁夏", "|银川|石嘴山|吴忠|固原"); 
		$this->area[14] = array("陕西", "|西安|宝鸡|咸阳|铜川|渭南|延安|榆林|汉中|安康|商洛"); 
		$this->area[15] = array("甘肃", "|兰州|嘉峪关|金昌|白银|天水|酒泉|张掖|武威|定西|陇南|平凉|庆阳|临夏|甘南"); 
		$this->area[16] = array("青海", "|西宁|海东|海南|海北|黄南|玉树|果洛|海西"); 
		$this->area[17] = array("新疆", "|乌鲁木齐|石河子|克拉玛依|伊犁|巴音郭勒|昌吉|克孜勒苏柯尔克孜|博尔塔拉|吐鲁番|哈密|喀什|和田|阿克苏"); 
		$this->area[18] = array("西藏", "|拉萨|日喀则|山南|林芝|昌都|阿里|那曲"); 
		$this->area[19] = array("湖北", "|武汉|宜昌|荆州|襄樊|黄石|荆门|黄冈|十堰|恩施|潜江|天门|仙桃|随州|咸宁|孝感|鄂州");
		$this->area[20] = array("湖南", "|长沙|常德|株洲|湘潭|衡阳|岳阳|邵阳|益阳|娄底|怀化|郴州|永州|湘西|张家界"); 
		$this->area[21] = array("四川", "|成都|绵阳|德阳|自贡|攀枝花|广元|内江|乐山|南充|宜宾|广安|达川|雅安|眉山|甘孜|凉山|泸州"); 
		$this->area[22] = array("江西", "|南昌市|景德镇|九江|鹰潭|萍乡|新馀|赣州|吉安|宜春|抚州|上饶"); 
		$this->area[23] = array("江苏", "|南京|镇江|苏州|南通|扬州|盐城|徐州|连云港|常州|无锡|宿迁|泰州|淮安"); 
		$this->area[24] = array("安徽", "|合肥|芜湖|蚌埠|马鞍山|淮北|铜陵|安庆|黄山|滁州|宿州|池州|淮南|巢湖|阜阳|六安|宣城|亳州"); 
		$this->area[25] = array("贵州", "|贵阳|六盘水|遵义|安顺|铜仁|黔西南|毕节|黔东南|黔南"); 
		$this->area[26] = array("浙江", "|杭州|宁波|温州|嘉兴|湖州|绍兴|金华|衢州|舟山|台州|丽水"); 
		$this->area[27] = array("福建", "|福州|厦门|莆田|三明|泉州|漳州|南平|龙岩|宁德"); 
		$this->area[28] = array("广东", "|广州|深圳|珠海|汕头|东莞|中山|佛山|韶关|江门|湛江|茂名|肇庆|惠州|梅州|汕尾|河源|阳江|清远|潮州|揭阳|云浮"); 
		$this->area[29] = array("广西", "|南宁|柳州|桂林|梧州|北海|防城港|钦州|贵港|玉林|南宁地区|柳州地区|贺州|百色|河池"); 
		$this->area[30] = array("云南", "|昆明|大理|曲靖|玉溪|昭通|楚雄|红河|文山|思茅|西双版纳|保山|德宏|丽江|怒江|迪庆|临沧");
		$this->area[31] = array("海南", "|海口|三亚"); 
		$this->area[32] = array("香港", ""); 
		$this->area[33] = array("澳门", ""); 
		$this->area[34] = array("台湾", "|台北|高雄|台中|台南|屏东|南投|云林|新竹|彰化|苗栗|嘉义|花莲|桃园|宜兰|基隆|台东|金门|马祖|澎湖");
		
		$this->net_provide_arr	=	array(
											'移动',
											'联通',
											'电信',
											'网通',
											'铁通',
											'卫通',
										);
		
		$this->section_arr	=	array(
									'华北'	=>	array('北京','天津','河北','山西','内蒙古'),
									'东北'	=>	array('辽宁','吉林','黑龙江'),
									'华东'	=>	array('上海','江苏','浙江','安徽','福建','江西','山东'),
									'中南'	=>	array('河南','湖北','湖南','广东','广西','海南'),
									'西南'	=>	array('重庆','四川','贵州','云南','西藏'),
									'西北'	=>	array('陕西','甘肃','青海','宁夏','新疆'),
									'港澳台'	=>	array('香港','澳门','台湾'),
									);
		
		$this->data_charset	=	'gb2312';
		$this->to_charset	=	'utf-8';
	}
	
	/**
	 * 读取纯真IP数据库资料
	 * @param string	$ip			要查询的IP地址
	 * @param string	$data_path	IP数据库文件所在路径[绝对路径或者相对于所调用该方法的文件所在路径，为空或路径对应的IP库文件不存在则使用默认IP库]
	 * @return string
	 */
	public function convertIP($ip = '', $data_path = ''){
		//IP数据库文件路径
		$dat_path	=	 empty($data_path)	?	dirname(__FILE__).'/Resource/QQWry.Dat'	:	$data_path;
		if (!is_file($dat_path)){
			if (!is_file(dirname(__FILE__).'/Resource/QQWry.Dat')){
				return 'IP date file not exists or access denied';
			}
			else{
				$dat_path	=	dirname(__FILE__).'/Resource/QQWry.Dat';
			}
		}
		
		//检查IP地址是否合法
		if(!preg_match("/^[1-9]{1}[0-9]{0,2}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/", $ip)) {
			return 'IP Address Error';
		}
		//打开IP库文件
		if(!$fd = @fopen($dat_path, 'rb')){
			return 'IP date file not exists or access denied';
		}
		
		//分解IP进行运算，得出整形数值用于查询地址
		$ip		=	explode('.', $ip);
		$ipNum	=	$ip[0] * 16777216 + $ip[1] * 65536 + $ip[2] * 256 + $ip[3];
		
		//获取IP数据索引的开始和结束位置
		$DataBegin	=	fread($fd, 4);
		$DataEnd	=	fread($fd, 4);
		$ipbegin	=	implode('', unpack('L', $DataBegin));
		if($ipbegin < 0) $ipbegin += pow(2, 32);
		$ipend		=	implode('', unpack('L', $DataEnd));
		if($ipend < 0) $ipend += pow(2, 32);
		$ipAllNum	=	($ipend - $ipbegin) / 7 + 1;
		
		$BeginNum	=	0;
		$EndNum		=	$ipAllNum;
		$ip1num		=	0;
		$ip2num		=	0;
		$ipAddr1	=	null;
		$ipAddr2	=	null;
		//使用二分法从索引记录中搜索匹配的IP记录
		while($ip1num>$ipNum || $ip2num<$ipNum) {
			$Middle	=	intval(($EndNum + $BeginNum) / 2);
			
			//指针偏移到索引位置读取4字字节数据
			fseek($fd, $ipbegin + 7 * $Middle);
			$ipData1	=	fread($fd, 4);
			if(strlen($ipData1) < 4) {
				fclose($fd);
				return 'System Error';
			}
			//提取出来的数据转换为长整型，如果为负则加上2的32次冥
			$ip1num	=	implode('', unpack('L', $ipData1));
			if($ip1num < 0) $ip1num += pow(2, 32);
			
			//提取的长整型数大于我们的IP地址则修改结束位置进行下一次循环
			if($ip1num > $ipNum) {
				$EndNum	=	$Middle;
				continue;
			}
			
			//取完上一个索引后取一下一索引
			$DataSeek	=	fread($fd, 3);
			if(strlen($DataSeek) < 3) {
				fclose($fd);
				return 'System Error';
			}
			$DataSeek	=	implode('', unpack('L', $DataSeek.chr(0)));
			fseek($fd, $DataSeek);
			$ipData2	=	fread($fd, 4);
			if(strlen($ipData2) < 4) {
				fclose($fd);
				return 'System Error';
			}
			$ip2num = implode('', unpack('L', $ipData2));
			if($ip2num < 0) $ip2num += pow(2, 32);
			
			//没找到则提示未知
			if($ip2num < $ipNum) {
				if($Middle == $BeginNum) {
					fclose($fd);
					return 'Unknown';
				}
				$BeginNum	=	$Middle;
			}
		}
		
		//
		$ipFlag	=	fread($fd, 1);
		if($ipFlag == chr(1)) {
			$ipSeek	=	fread($fd, 3);
			if(strlen($ipSeek) < 3) {
				fclose($fd);
				return 'System Error';
			}
			$ipSeek	=	implode('', unpack('L', $ipSeek.chr(0)));
			fseek($fd, $ipSeek);
			$ipFlag	=	fread($fd, 1);
		}
		
		if($ipFlag == chr(2)) {
			$AddrSeek	=	fread($fd, 3);
			if(strlen($AddrSeek) < 3) {
				fclose($fd);
				return 'System Error';
			}
			$ipFlag	=	fread($fd, 1);
			if($ipFlag == chr(2)) {
				$AddrSeek2	=	fread($fd, 3);
				if(strlen($AddrSeek2) < 3) {
					fclose($fd);
					return 'System Error';
				}
				$AddrSeek2	=	implode('', unpack('L', $AddrSeek2.chr(0)));
				fseek($fd, $AddrSeek2);
			} else {
				fseek($fd, -1, SEEK_CUR);
			}
			
			while(($char = fread($fd, 1)) != chr(0))
				$ipAddr2	.=	$char;
			
			$AddrSeek	=	implode('', unpack('L', $AddrSeek.chr(0)));
			fseek($fd, $AddrSeek);
			
			while(($char = fread($fd, 1)) != chr(0))
				$ipAddr1	.=	$char;
		} else {
			fseek($fd, -1, SEEK_CUR);
			while(($char = fread($fd, 1)) != chr(0))
				$ipAddr1	.=	$char;
			
			$ipFlag	=	fread($fd, 1);
			if($ipFlag == chr(2)) {
				$AddrSeek2	=	fread($fd, 3);
				if(strlen($AddrSeek2) < 3) {
					fclose($fd);
					return 'System Error';
				}
				$AddrSeek2	=	implode('', unpack('L', $AddrSeek2.chr(0)));
				fseek($fd, $AddrSeek2);
			} else {
				fseek($fd, -1, SEEK_CUR);
			}
			while(($char = fread($fd, 1)) != chr(0)){
				$ipAddr2	.=	$char;
			}
		}
		fclose($fd);
		
		//作相应的替换操作后返回查询结果
		if(preg_match('/http/i', $ipAddr2)) {
			$ipAddr2	=	'';
		}
		$ipaddr	=	"$ipAddr1 $ipAddr2";
		$ipaddr	=	preg_replace('/CZ88.Net/is', '', $ipaddr);
		$ipaddr	=	preg_replace('/^s*/is', '', $ipaddr);
		$ipaddr	=	preg_replace('/s*$/is', '', $ipaddr);
		if(preg_match('/http/i', $ipaddr) || $ipaddr == '') {
			$ipaddr	=	'Unknown';
		}
		
		//若源文件编码跟所需编码不同则作转码然后返回
		if ($this->data_charset != $this->to_charset){
			$ipaddr	=	iconv($this->data_charset, $this->to_charset, $ipaddr);
		}
		
		return $ipaddr;
	}
	
	/**
	 * 取得IP地址所在地区资料
	 * country -> 该IP所在国家或地区名称	province -> 该IP所在省份名称[仅对中国使用]	city -> 该IP所在城市名称[中国大陆地区使用]
	 * @param string	$ip			所要查询的IP地址
	 * @param string	$data_path	IP数据库文件所在路径[绝对地址或者相对调用该方法的文件所在的相对路径，为空或路径对应的IP库文件不存在则使用默认IP库]
	 * @return string
	 */
	public function getIPArea($ip = '', $data_path = ''){
		$ipaddr	=	$this->convertIP($ip, $data_path);
		$this->net_provide	=	'';
		$this->section	=	'';
		
		if ($ipaddr == '' && $ip != ''){
			$ipaddr	=	$this->convertIP($ip);
		}
		
		if ($ipaddr == '' || strtoupper($ipaddr) == 'UNKNOWN'){
			return 'Unknown';
		}
		
		$tmp	=	explode(' ', $ipaddr);
		$pos	=	$tmp[0];
		foreach($this->area as $key => $value){
			$province	=	trim($value[0]);
			$citys		=	explode('|', $value[1]);
			$city		=	'';
			
			foreach($this->net_provide_arr as $net_key => $net_value){
				if (strstr($ipaddr, $net_value)){
					$this->net_provide	=	$net_value;
					break;
				}
			}
			
			if (strstr($pos, $province)){
				foreach($citys as $c_key => $city1){
					if ($city1 != ''){
						if (strstr($pos, $city1)){
							$city	=	$city1;
							break;
						}
						else{
							$city	=	'';
						}
					}
				}
				$province	=	$province;
				$this->province_id	=	$key;
				$country	=	'中国';
				
		
				foreach($this->section_arr as $sect => $sec_value){
					if (in_array($province, $sec_value)){
						$this->section	=	$sect;
						break;
					}
					else{
						$this->section	=	'';
					}
				}
				
				break;
			}
			else{
				$province	=	'';
			}
		}
		if ($this->country == ''){
			$start	=	strpos($pos, '国');
			if (!is_bool($start)){
				$start	=	$start + 3;
			}
			if ($start > 0){
				$country	=	substr($pos, 0, $start);
			}
			else{
				$country	=	'';
			}
		}
		
		$this->country	=	isset($country)?$country:$this->country;
		$this->province	=	isset($province)?$province:$this->province;
		$this->city		=	isset($city)?$city:$this->city;
		
		return $ipaddr;
	}
}
?>